package com.freewater.javabase.demo.mutithread.synchronize;

import java.util.concurrent.locks.StampedLock;

/**
 * 前面介绍的ReadWriteLock可以解决多线程同时读，但只有一个线程能写的问题。
 * <p>
 * 如果我们深入分析ReadWriteLock，会发现它有个潜在的问题：如果有线程正在读，写线程需要等待读线程释放锁后才能获取写锁，即读的过程中不允许写，这是一种悲观的读锁。
 * <p>
 * 要进一步提升并发执行效率，Java 8引入了新的读写锁：StampedLock。
 * <p>
 * StampedLock和ReadWriteLock相比，改进之处在于：读的过程中也允许获取写锁后写入！这样一来，我们读的数据就可能不一致，所以，需要一点额外的代码来判断读的过程中是否有写入，这种读锁是一种乐观锁。
 * <p>
 * 可见，StampedLock把读锁细分为乐观读和悲观读，能进一步提升并发效率。但这也是有代价的：一是代码更加复杂，二是StampedLock是不可重入锁，不能在一个线程中反复获取同一个锁。
 */
public class StampedLockDemo {

    public static void main(String[] args) {
        Point point = new Point();
        point.x = 100;
        point.y = 200;
        new Thread(() -> {
            try {
                point.move(100, 200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            System.out.println(point.distanceFromOrigin());
        }).start();
    }

    public static class Point {
        private double x;
        private double y;

        private final StampedLock lock = new StampedLock();

        public void move(double deltaX, double deltaY) throws InterruptedException {
            long stamp = lock.writeLock();
            try {
                x = x + deltaX;
                Thread.sleep(20);
                y = y + deltaY;
            } finally {
                lock.unlockWrite(stamp);
            }
        }

        public double distanceFromOrigin() {
            long stamp = lock.tryOptimisticRead(); // 获得一个乐观读锁
            // 注意下面两行代码不是原子操作
            // 假设x,y = (100,200)
            double currentX = x;
            // 此处已读取到x=100，但x,y可能被写线程修改为(300,400)
            double currentY = y;
            // 此处已读取到y，如果没有写入，读取是正确的(100,200)
            // 如果有写入，读取是错误的(100,400)
            if (!lock.validate(stamp)) {
                stamp = lock.readLock();
                System.out.println("进入悲观读取锁");
                try {
                    currentX = x;
                    currentY = y;
                } finally {
                    lock.unlockRead(stamp);// 释放悲观锁
                }
            }
            return Math.sqrt(currentX * currentX + currentY * currentY);
        }
    }
}
